package com.linxi.rpccompensation.config;

import com.linxi.rpccompensation.entity.SysJob;
import com.linxi.rpccompensation.mapper.JobMapper;
import com.linxi.rpccompensation.util.SpringUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;

import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

/**
 * 参考于开源项目
 * https://github.com/xuchao6969/timetask
 * 偶然发现这位老哥写的和我想要实现的功能基本一直，于是乎拿过来二次开发
 *
 * @author lx
 */
@Configuration
@EnableScheduling
public class DynamicScheduleTask implements SchedulingConfigurer {

	@Resource
	JobMapper jobMapper;

	protected static Logger logger = LoggerFactory.getLogger(SchedulingConfigurer.class);

	private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	private static ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
	private static Map<String, ScheduledFuture<?>> scheduledFutureMap = new ConcurrentHashMap<>();
	private final static String METHOD = "runner";

	static {
		threadPoolTaskScheduler.initialize();
	}

	/**
	 * 从数据库里取得所有要执行的定时任务
	 *
	 * @return
	 * @throws Exception
	 */
	private List<SysJob> getAllTasks() {
		List<SysJob> list = jobMapper.queryJobList();
		return list;
	}

	/**
	 * 核心配置
	 *
	 * @param taskRegistrar
	 */
	@Override
	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
		List<SysJob> tasks = getAllTasks();
		logger.info("定时任务启动,预计启动任务数量=" + tasks.size() + "; time=" + sdf.format(new Date()));

		//校验数据（这个步骤主要是为了打印日志，可以省略）
		checkDataList(tasks);

		//通过校验的数据执行定时任务
		int count = 0;
		if (tasks.size() > 0) {
			for (SysJob task : tasks) {
				try {
					start(task);
					count++;
				} catch (Exception e) {
					logger.error("定时任务启动错误:" + task.getName() + ";" + task.getDesc() + ";" + e.getMessage());
				}
			}
		}
		logger.info("定时任务实际启动数量=" + count + "; time=" + sdf.format(new Date()));
	}

	/**
	 * runnable支持函数式接口，支持以lambda的写法代理重写run方法
	 *
	 * @param task
	 * @return
	 */
	private static Runnable getRunnable(SysJob task) {
		return () -> {
			try {
				Object obj = SpringUtil.getBean(task.getTarget());
				Method method = obj.getClass().getMethod(METHOD);
				method.invoke(obj);
			} catch (InvocationTargetException e) {
				logger.error("定时任务启动错误，反射异常:" + task.getTarget() + ";" + METHOD + ";" + e.getMessage());
			} catch (Exception e) {
				logger.error(e.getMessage());
			}
		};
	}

	/**
	 * 同上，Trigger也支持函数式接口
	 *
	 * @param task
	 * @return
	 */
	private static Trigger getTrigger(SysJob task) {
		return triggerContext -> {
			//将Cron 0/1 * * * * ? 输入取得下一次执行的时间
			CronTrigger trigger = new CronTrigger(task.getCron());
			Date nextExec = trigger.nextExecutionTime(triggerContext);
			return nextExec;
		};
	}

	/**
	 * 检查列表
	 *
	 * @param list
	 * @return
	 */
	private List<SysJob> checkDataList(List<SysJob> list) {
		String errMsg = "";
		for (int i = 0; i < list.size(); i++) {
			if (!checkOneData(list.get(i)).equalsIgnoreCase("success")) {
				errMsg += list.get(i).getName() + ";";
				list.remove(list.get(i));
				i--;
			}
		}
		if (!StringUtils.isBlank(errMsg)) {
			logger.error("未启动的任务:" + errMsg);
		}
		return list;
	}

	/**
	 * 检查单个
	 *
	 * @param task
	 * @return
	 */
	public static String checkOneData(SysJob task) {
		String result = "success";
		try {
			Class cal = Class.forName(task.getTarget());
			Object obj = SpringUtil.getBean(cal);
			Method method = obj.getClass().getMethod(METHOD);
			String cron = task.getCron();
			if (StringUtils.isBlank(cron)) {
				result = "定时任务启动错误，无cron:" + task.getName();
				logger.error(result);
			}
		} catch (ClassNotFoundException e) {
			result = "定时任务启动错误，找不到类:" + task.getTarget() + e.getMessage();
			logger.error(result);
		} catch (NoSuchMethodException e) {
			result = "定时任务启动错误，找不到方法,方法必须是public:" + task.getTarget() + ";" + METHOD + ";" + e.getMessage();
			logger.error(result);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		return result;
	}

	/**
	 * 启动定时任务
	 *
	 * @param task
	 * @param
	 */
	public static void start(SysJob task) {
		ScheduledFuture<?> scheduledFuture = threadPoolTaskScheduler.schedule(getRunnable(task), getTrigger(task));
		scheduledFutureMap.put(task.getId(), scheduledFuture);
		logger.info("启动定时任务" + task.getId());
	}

	/**
	 * 取消定时任务
	 *
	 * @param task
	 */
	public static void cancel(SysJob task) {
		ScheduledFuture<?> scheduledFuture = scheduledFutureMap.get(task.getId());
		if (scheduledFuture != null && !scheduledFuture.isCancelled()) {
			scheduledFuture.cancel(Boolean.FALSE);
		}
		scheduledFutureMap.remove(task.getId());
		logger.info("取消定时任务" + task.getId());
	}

}
